package com.naza.rpc.consumer.balance.manage;

import com.naza.rpc.discovery.*;
import com.naza.rpc.model.ServiceInstance;
import com.naza.rpc.consumer.balance.LoadBalancer;
import com.naza.rpc.consumer.balance.LoadBalancerFactory;
import com.naza.rpc.model.LoadBalance;
import com.naza.rpc.netty.RpcSendExecutor;
import com.naza.rpc.util.PropertiesUtil;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author yl
 */
public class ClientManage {

    public static ClientManage INSTANCE = new ClientManage();

    private Object LOCK = new Object();

    private Discovery discovery;

    private RpcSendExecutor messageSendExecutor = RpcSendExecutor.getInstance();

    // serviceName,instances
    private Map<String, List<ServiceInstance>> instanceMap = new ConcurrentHashMap<>();

    private Map<String,DiscoveryListener> monitorMap = new ConcurrentHashMap<>();

    public ClientManage(){
        synchronized (LOCK){
            if(null == INSTANCE){
                synchronized (LOCK){
                    if(null == INSTANCE){
                        init();
                    }
                }
            }
        }
    }

    private void init(){
        String discoveryMode = PropertiesUtil.getINSTANCE().getValue("discovery.mode");
        if("nacos".contains(discoveryMode)){
            discovery = new NacosDiscovery();
        }
        if("consul".contains(discoveryMode)){
            discovery = new ConsulDiscovery();
        }
        if("zookeeper".contains(discoveryMode)){
            discovery = new ZookeeperDiscovery();
        }
    }

    public void addService(String serviceName){
        if(!instanceMap.containsKey(serviceName)){
            //第一次调用该服务，要从注册中心将该服务的所有instances下载下来，并执行监听
            List<ServiceInstance> instances = new ArrayList<>();
            List<ServiceInstance> current = discovery.getAll(serviceName);
            instances.addAll(current);
            instanceMap.put(serviceName,instances);
        }
    }

    public void monitor(String serviceName){
        if(!monitorMap.containsKey(serviceName)){
            DiscoveryListener listener = new DiscoveryListener() {
                @Override
                public void instanceChanged(List<ServiceInstance> existing) {
                    //全量更新本地缓存,TODO;线程安全?
                    Set<String> oldAddr = getAddrList(instanceMap);
                    instanceMap.remove(serviceName);
                    instanceMap.put(serviceName,existing);
                    Set<String> newAddr = getAddrList(instanceMap);
                    Set<String> needStop = calculateNeedStopConnect(oldAddr,newAddr);
                    messageSendExecutor.stopConnect(needStop);
                }
            };
            discovery.monitor(serviceName, listener);
            monitorMap.put(serviceName,listener);
        }
    }

    public ServiceInstance selectClient(String serviceName,LoadBalance load){
        LoadBalancer loadBalancer = LoadBalancerFactory.getLoadBalancer(load);
        ServiceInstance instance = loadBalancer.selectClient(serviceName,instanceMap.get(serviceName));
        return instance;
    }

    public List<ServiceInstance> getByServiceName(String serviceName){
        return instanceMap.get(serviceName);
    }

    public Set<String> calculateNeedStopConnect(Set<String> oldList,Set<String> newList){
        Set<String> needStopConnect = new HashSet<>();
        for(String old:oldList){
            if(!newList.contains(old)){
                needStopConnect.add(old);
            }
        }
        return needStopConnect;
    }

    private Set<String> getAddrList(Map<String, List<ServiceInstance>> instanceMap){
        Set<String> addrList = new HashSet<>();
        for(Map.Entry<String, List<ServiceInstance>> serviceInstancesEntry : instanceMap.entrySet()){
            List<ServiceInstance> serviceInstances = serviceInstancesEntry.getValue();
            for(ServiceInstance instance:serviceInstances){
                addrList.add(instance.getIp()+":"+instance.getPort());
            }
        }
        return addrList;
    }

}
